
package nsalt

import chisel3._
import chisel3.util._
import chisel3.util.experimental.BoringUtils

import nsalt.decode._
import nsalt.util._
import nsalt.mem._

// Sequential Instr Issue Unit 
// https://github.com/OSCPU/NutShell/blob/fd86beadfc47f52973270ce6109edebd2a30363b/src/main/scala/nutcore/backend/seq/ISU.scala
// ============================================================================
// 
// REFACTORING NOTE:
// 
// Issue stage is also the maintainer of the register file, where all register
// values held. Issue stage has two tasks:
// 
// 1) Read the value of registers referred in the instruction, and forward them
//    to the next stage (execution). This action is called "Register Read".
// 
// 2) Write (update) register referred in the instruction with values calculated
//    by ALU, or fetched from external mem through LSU. This action is typically
//    part of "commit", return by Write-Back stage. 

class Issue extends Module with Config with RegFileParam {

  // REFACTORING NOTE: 
  // 
  // If it's your first time coming here and have no idea about what regfile is,
  // then remember that this is the physical registers that you can access with
  // name in t0, t1 in assembly language.
  //
  // Issue stage is the maintainer of register files.

  val regfile = new RegFile

  // REFACTORING NOTE: 
  //
  // The port naming is a little bit misleading. Both Write-back and Execute stage
  // may involve register file updating, and both of them are called "forward". 
  // 
  // In the original Nutshell code, "forward" is explicitly for "forward from execute",
  // while "wb" stands for "forward from write-back". there is a "wb" inside "forward",
  // which could be because ForwardIO (in nutshell) reuses WriteBackIO.
  // 
  // In the refactored version:
  // io.wb -> ~.writeback
  // (check port definition for changes below)
  // WriteBackIO -> WriteRegFilePort
  // WriteBackIO.rfWen -> ~.enableW
  // WriteBackIO.rfData -> ~.data
  // WriteBackIO.rfDest -> ~.dest
  // ForwardIO -> ForwardPort
  // ForwardIO.wb -> ~.writeReg

  val io = IO(new Bundle {
    // val in = Vec(2, Flipped(Decoupled(new DecodePort))) // make in-order backend compatible with high performance frontend 
    val in = Flipped(Decoupled(new DecodePort())) // make in-order backend compatible with high performance frontend 
    val out = Decoupled(new DecodePort())
    val writeback = Flipped(new WriteRegFilePort())
    val forward = Flipped(new ForwardPort())
    val flush = Input(Bool())
  })

  io.out.bits := DontCare

  val src1Ref = io.in.bits.ctrlSgnl.src1Ref
  val src2Ref = io.in.bits.ctrlSgnl.src2Ref
  val regDest = io.in.bits.ctrlSgnl.destRef

  // `isReliant`, or formally `isDepend`, indicates that source and destination
  // register reference is 

  def isReliant(srcRef: UInt, regDest: UInt, regWrite: Bool): Bool = {
    (srcRef =/= 0.U) && (srcRef === regDest) && regWrite
  }

  // ============================================================================
  // HANDLING DATA HAZARD
  // 
  // There are two types of data hazard identified here:
  // 
  // 1) The incoming instruction is trying to access register to be written by
  //    Execute Stage. 
  // 
  // 2) The incoming instruction is trying to access register to be updated by
  //    Write-Back stage.

  // If function unit type of instruction is neither ALU nor LSU, instruction will
  // not be forwarded. 
  val notForward = (io.forward.funcType =/= FuncType.alu) && (io.forward.funcType =/= FuncType.lsu)

  // If Execute Stage writes register.
  val forwardRegWrite = io.forward.writeReg.enableW && io.forward.valid

  // Data Hazard Case #1
  val src1ReliantEX = isReliant(src1Ref, io.forward.writeReg.dest, forwardRegWrite)
  val src2ReliantEX = isReliant(src2Ref, io.forward.writeReg.dest, forwardRegWrite)

  // Data Hazard Case #2
  val src1ReliantWB = isReliant(src1Ref, io.writeback.dest, io.writeback.enableW)
  val src2ReliantWB = isReliant(src2Ref, io.writeback.dest, io.writeback.enableW)

  // If operand relying to Execute Stage, and SHOULD forward, then mark to forward
  // next cycle.
  val src1ForwardNextCycle = src1ReliantEX && !notForward
  val src2ForwardNextCycle = src2ReliantEX && !notForward

  // If operand relying to Write-Back stage, forward current cycle.
  val src1Forward = src1ReliantWB && Mux(notForward, !src1ReliantEX, true.B)
  val src2Forward = src2ReliantWB && Mux(notForward, !src2ReliantEX, true.B)

  val sb = new ScoreBoard
  val src1Ready = !sb.isBusy(src1Ref) || src1ForwardNextCycle || src1Forward
  val src2Ready = !sb.isBusy(src2Ref) || src2ForwardNextCycle || src2Forward
  io.out.valid := io.in.valid && src1Ready && src2Ready


  // Output Source Operand 1:
  // 1) If the operand type is PC, then ouput the PC from controlFlowPort.
  // 
  // 2) If the operand is marked to forward next cycle, indicating reliant
  //    to execute stage. Replace the operand with value from execute stage.
  // 
  // 3) If the operand is marked to forward current cycle, indicating
  //    reliant to write-back stage, replace with operand with value from
  //    write-back stage.
  // AddrBits
  // 4) If none of the cases above, read from register file. 

  io.out.bits.data.src1 := Mux1H(List(
    (io.in.bits.ctrlSgnl.src1Type === SrcType.pc) -> SignExt(io.in.bits.ctrlFlow.pc, ADDR_BITS),
    src1ForwardNextCycle -> io.forward.writeReg.data, //io.forward.wb.regData,
    (src1Forward && !src1ForwardNextCycle) -> io.writeback.data, //io.writeback.regData,
    ((io.in.bits.ctrlSgnl.src1Type =/= SrcType.pc) && !src1ForwardNextCycle && !src1Forward) -> regfile.read(src1Ref),
  ))

  io.out.bits.data.src2 := Mux1H(List(
    (io.in.bits.ctrlSgnl.src2Type =/= SrcType.reg) -> io.in.bits.data.imm,
    src2ForwardNextCycle -> io.forward.writeReg.data, //io.forward.wb.regData,
    (src2Forward && !src2ForwardNextCycle) -> io.writeback.data, //io.writeback.regData,
    ((io.in.bits.ctrlSgnl.src2Type === SrcType.reg) && !src2ForwardNextCycle && !src2Forward) -> regfile.read(src2Ref)
  ))

  io.out.bits.data.imm  := io.in.bits.data.imm

  io.out.bits.ctrlFlow <> io.in.bits.ctrlFlow
  io.out.bits.ctrlSgnl := io.in.bits.ctrlSgnl
  io.out.bits.ctrlSgnl.isSrc1Forward := src1ForwardNextCycle
  io.out.bits.ctrlSgnl.isSrc2Forward := src2ForwardNextCycle

  // returned request of updating regfile (a.k.a "commit")
  when (io.writeback.enableW) {
    regfile.write(io.writeback.dest, io.writeback.data)
  }

  // ============================================================================
  // Scoreboard Update
  // 
  // If both writeback commit and execute return are updating same register, then
  // update the scoreboard for corresponding register.
  // 
  val isForwardRelient = isReliant(io.writeback.dest, io.forward.writeReg.dest, forwardRegWrite)
 
  val writeBackClearMask = Mux(io.writeback.enableW && !isForwardRelient,
    sb.mask(io.writeback.dest),
    0.U(REG_NUM.W)
  )
 
  val isuFireSetMask = Mux(io.out.fire, sb.mask(regDest), 0.U)
  when (io.flush) {
    sb.update(0.U, Fill(REG_NUM, 1.U(1.W)))
  }
  .otherwise {
    sb.update(isuFireSetMask, writeBackClearMask)
  }

  io.in.ready := !io.in.valid || io.out.fire
  // io.in(1).ready := false.B
}
